//-------------------------------------------------------------------------------------
// <copyright file="WixTaskBase.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation. All rights reserved.
//      THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
//      EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
//      WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// </copyright>
// <author>Greg Beech</author>
// <email>gregbee@microsoft.com</email>
// <date>2004-04-16</date>
// <summary>
//      Base class for tasks relating to Wix, the Microsoft tool to build MSI packages 
//      from XML source files.
// </summary>  
//-------------------------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.Wix
{
    #region Using directives

    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using Microsoft.Build.Framework;

    #endregion

    /// <summary>
    /// Base class for tasks relating to <b>Wix</b>, the Microsoft tool to build MSI packages from XML 
    /// source files.
    /// </summary>
    /// <remarks>
    /// <para>
    /// <b>About Wix:</b> Wix was originally a Microsoft tool that has been released into the open source
    /// community under the Common Public License. The binaries and source code can be downloaded from
    /// <a href="http://sourceforge.net/projects/wix">SourceForge</a>.
    /// </para>
    /// </remarks>
    public abstract class WixTaskBase : TaskBase
    {
        #region Constants

        /// <summary>
        /// The name of the compiler executable which pre-processes and compiles the XML source files.
        /// </summary>
        protected const string CompilerName = "candle.exe";

        /// <summary>
        /// The name of the decompiler executable which creates XML files from MSI packages.
        /// </summary>
        protected const string DecompilerName = "dark.exe";

        /// <summary>
        /// The name of the linker executable which links the compiled modules.
        /// </summary>
        protected const string LinkerName = "light.exe";

        /// <summary>
        /// The return code that the Wix executables return when they have successfully run.
        /// </summary>
        protected const int WixSuccessCode = 0;

        #endregion

        #region Member Variables

        private int informationLevel = 0;
        private string outputFile;
        private string[] sourceFiles;
        private bool treatWarningsAsErrors = false;
        private int warningLevel = 0;
        private string wixPath;
        private string localeFile;
        private bool echoOutput = false;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the level of information displayed by the tool.
        /// </summary>
        /// <value>
        /// An integer between zero (verbose information) and three (no information).
        /// </value>
        public int InformationLevel
        {
            get
            {
                return this.informationLevel;
            }

            set
            {
                if (value < 0 || value > 3)
                {
                    throw new ArgumentOutOfRangeException("value", value, "The information level must be between 0 (verbose information) and 3 (no information).");
                }

                this.informationLevel = value;
            }
        }

        /// <summary>
        /// Gets or sets the path to the intermediate object file to produce. This should have the 
        /// extension *.wxsobj.
        /// </summary>
        /// <value>
        /// The path to the intermediate object file to produce.
        /// </value>
        [Required]
        public string OutputFile
        {
            get { return this.outputFile; }
            set { this.outputFile = value; }
        }

        /// <summary>
        /// Gets or sets the source file(s). These will normally have the extension *.wxs. Please refer to 
        /// the Wix documentation for the schema of these files.
        /// </summary>
        /// <value>
        /// The source file(s).
        /// </value>
        [Required]
        public string[] SourceFiles
        {
            get { return this.sourceFiles ?? new string[0]; }
            set { this.sourceFiles = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether warnings are treated as errors.
        /// </summary>
        /// <value>
        /// <b>true</b> if warnings are treated as errors, or <b>false</b> otherwise.
        /// </value>
        public bool TreatWarningsAsErrors
        {
            get { return this.treatWarningsAsErrors; }
            set { this.treatWarningsAsErrors = value; }
        }

        /// <summary>
        /// Gets or sets the level of warnings displayed by the tool.
        /// </summary>
        /// <value>
        /// An integer between zero (all warnings) and three (no warnings).
        /// </value>
        public int WarningLevel
        {
            get
            {
                return this.warningLevel;
            }

            set
            {
                if (value < 0 || value > 3)
                {
                    throw new ArgumentOutOfRangeException("value", value, "The warning level must be between 0 (all warnings) and 3 (no warnings).");
                }

                this.warningLevel = value;
            }
        }

        /// <summary>
        /// Gets or sets the path to the directory that the Wix executables reside in.
        /// </summary>
        /// <value>
        /// The path to the directory that the Wix executables reside in.
        /// </value>
        [Required]
        public string WixPath
        {
            get { return this.wixPath; }
            set { this.wixPath = value; }
        }

        /// <summary>
        /// Gets or sets the localeFile path (-loc option of light.exe)
        /// </summary>
        /// <value>
        /// A path to the locale file
        /// </value>
        public string LocaleFile
        {
            get { return this.localeFile; }
            set { this.localeFile = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to echoOutput
        /// </summary>
        /// <value>
        /// A bool signifying if the output from the Wix commands should be echoed to the msbuild cmdline output
        /// </value>
        public bool EchoOutput
        {
            get { return this.echoOutput; }
            set { this.echoOutput = value; }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Escapes an argument so that it is interpreted correctly. This does things like enclosing in quotes if there
        /// are spaces and escaping quotes.
        /// </summary>
        /// <param name="argument">
        /// The argument to escape.
        /// </param>
        /// <returns>
        /// The escaped argument.
        /// </returns>
        protected static string EscapeArgument(string argument)
        {
            if (string.IsNullOrEmpty(argument))
            {
                // a pair of quotes with nothing in between
                return "\"\"";
            }
            else
            {
                // replace any quotes with double quotes
                if (argument.Contains("\""))
                {
                    argument = argument.Replace("\"", "\"\"");
                }

                // wrap with quotes if it contains any spaces
                if (argument.Contains(" "))
                {
                    argument = string.Concat("\"", argument, "\"");
                }

                return argument;
            }
        }

        /// <summary>
        /// Appends the information level and warning level arguments.
        /// </summary>
        /// <param name="argumentsBuilder">
        /// The <see cref="System.Text.StringBuilder"/> being used to create the arguments for the Wix process.
        /// </param>
        protected void AppendInformationAndWarningLevelArguments(StringBuilder argumentsBuilder)
        {
            argumentsBuilder.AppendFormat(CultureInfo.InvariantCulture, "-w{0} ", this.warningLevel);
            argumentsBuilder.AppendFormat(CultureInfo.InvariantCulture, "-v{0} ", this.informationLevel);
        }

        /// <summary>
        /// Runs the Wix process with the specified arguments, waits for it to finish, and checks that it
        /// was successful.
        /// </summary>
        /// <param name="application">
        /// The name of the Wix application to run.
        /// </param>
        /// <param name="arguments">
        /// The argument string to pass to the application.
        /// </param>
        /// <exception cref="System.ApplicationException">
        /// Thrown when the return code of the application indicates that the process has failed.
        /// </exception>
        protected void RunWixProcess(string application, string arguments)
        {
            this.Log.LogMessage("Running \"" + application + "\" with arguments \"" + arguments + "\".");
            
            // create the startup info
            ProcessStartInfo startInfo = new ProcessStartInfo(application, arguments);
            startInfo.CreateNoWindow = true;
            startInfo.ErrorDialog = false;
            startInfo.LoadUserProfile = false;
            startInfo.UseShellExecute = false;
            if (this.echoOutput)
            {
                startInfo.RedirectStandardOutput = true;
                startInfo.RedirectStandardError = true;
            }

            startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            startInfo.WorkingDirectory = Path.GetDirectoryName(this.wixPath);

            // create the process and wait for it to finish
            Process wixProcess = Process.Start(startInfo);
            if (this.echoOutput)
            {
                wixProcess.OutputDataReceived += this.WixOutputOutputHandler;
                wixProcess.ErrorDataReceived += this.WixOutputErrorHandler;
                wixProcess.BeginOutputReadLine();
                wixProcess.BeginErrorReadLine();
            }

            wixProcess.WaitForExit();

            // check the process return code and throw an exception if it is not the success code
            if (wixProcess.ExitCode != WixTaskBase.WixSuccessCode)
            {
                throw new ApplicationException(string.Format(
                                                   CultureInfo.InvariantCulture,
                                                   "The Wix application \"{0}\" exited with code {1} indicating that the process failed.",
                                                   application,
                                                   wixProcess.ExitCode));
            }
        }

        private void WixOutputOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
        {
            if (!String.IsNullOrEmpty(outLine.Data))
            {
                this.Log.LogMessage(outLine.Data);
            }
        }

        private void WixOutputErrorHandler(object sendingProcess, DataReceivedEventArgs outLine)
        {
            if (!String.IsNullOrEmpty(outLine.Data))
            {
                this.Log.LogError(outLine.Data);
            }
        }

        #endregion
    }
}